﻿// (c) Copyright 2013 Mike Riedel.
// This source is subject to the Microsoft Public License (Ms-PL).
// All other rights reserved.

using System;
using System.Threading;

namespace FreshMeat.Threading
{
   /// <summary>
   /// Reader-writer lock which allows multiple readers but gives priority to writers
   /// to avoid write-starvation.
   /// </summary>
   /// <remarks>
   /// Follows the well-known implementation described here:
   /// http://en.wikipedia.org/wiki/Readers-writers_problem
   /// http://www.rfc1149.net/blog/2011/01/07/the-third-readers-writers-problem/
   /// </remarks>
   public class ReaderWriterLock : IReaderWriterLock
   {
      readonly Reader reader;
      readonly Writer writer;
      readonly object onlyOneReaderWaitingForRead = new object();
      readonly object write = new object();
      readonly object read = new object();
      int readCount;
      int writeCount;

      class Reader : ILock
      {
         readonly ReaderWriterLock parent;
         readonly LockContext context;

         public Reader(ReaderWriterLock parent)
         {
            this.parent = parent;
            context = new LockContext(Done);
         }

         public void Wait()
         {
            lock (parent.onlyOneReaderWaitingForRead)
               lock (parent.read)
                  if (Interlocked.Increment(ref parent.readCount) == 1)
                     Monitor.Enter(parent.write);
         }

         public void Done()
         {
            if (Interlocked.Decrement(ref parent.readCount) == 0)
               Monitor.Exit(parent.write);
         }

         public IDisposable Context()
         {
            Wait();
            return context;
         }
      }

      class Writer : ILock
      {
         readonly ReaderWriterLock parent;
         readonly LockContext context;

         public Writer(ReaderWriterLock parent)
         {
            this.parent = parent;
            context = new LockContext(Done);
         }

         public void Wait()
         {
            if (Interlocked.Increment(ref parent.writeCount) == 1)
               Monitor.Enter(parent.read);

            Monitor.Enter(parent.write);
         }

         public void Done()
         {
            Monitor.Exit(parent.write);

            if (Interlocked.Decrement(ref parent.writeCount) == 0)
               Monitor.Exit(parent.read);
         }

         public IDisposable Context()
         {
            Wait();
            return context;
         }
      }

      /// <summary>
      /// Creates instance using specified parameters.
      /// </summary>
      public ReaderWriterLock()
      {
         reader = new Reader(this);
         writer = new Writer(this);
      }

      /// <summary>
      /// Gets the lock for read access.
      /// </summary>
      /// <returns></returns>
      public ILock Read()
      {
         return reader;
      }

      /// <summary>
      /// Gets the lock for write access.
      /// </summary>
      /// <returns></returns>
      public ILock Write()
      {
         return writer;
      }
   }
}